#ifndef DIRECTORY_H
#define DIRECTORY_H

#include <Globals.h>
#include <File.h>
#include <Map.h>
#include <Filesystem.h>
#include <Kernel.h>
#include <ResourceManager.h>
#include <ProcessManager.h>

/**
  Syscall interface.
**/
namespace Directory_IF
{
  const int addEntry          =0;
  const int getEntry          =1;
  const int getEntryN         =2;
  const int getNEntries       =3;
  const int removeEntry       =4;
  const int mount             =5;
  const int unmount           =6;
  const int mknod             =7;
};

/**
  These are the function IDs the Directory will expect to be
  able to use on the resource given.
**/
#define DIR_ADDENTRY    1600
#define DIR_REMOVEENTRY 1601
#define DIR_GETENTRY    1602
#define DIR_POPULATE    1603

/**
  An abstraction of a directory.
**/
class Directory : public File
{
public:
  /**
    Generic constructor
  **/
  Directory(char *name, Filesystem *parent, u32int resourceId) :
    File(name, parent, resourceId, eFileType_Directory),
    mountpoint(NULL),
    entries(DirectoryEntryMap::stringEqPredicate,
            DirectoryEntryMap::stringHashFunction)
  {
    populated = false;
    resource->add((Resource::Call1)&Directory::addEntryResource, Directory_IF::addEntry);
    resource->add((Resource::Call1)&Directory::removeEntry,      Directory_IF::removeEntry);
    resource->add((Resource::Call1)&Directory::getEntryResource, Directory_IF::getEntry);
    resource->add((Resource::Call1)&Directory::getEntryNResource,Directory_IF::getEntryN);
    resource->add((Resource::Call0)&Directory::getNEntries,      Directory_IF::getNEntries);
    resource->add((Resource::Call1)&Directory::mountResource,    Directory_IF::mount);
    resource->add((Resource::Call0)&Directory::unmount,          Directory_IF::unmount);
    resource->add((Resource::Call4)&Directory::mknod,            Directory_IF::mknod);
  }

  ~Directory()
  {
  }
  
  /**
     Add a child entry to this directory.
  **/
  u32int addEntry(File *entry)
  {
    // If we are a mountpoint, relay the command directly to the next filesystem.
    if (mountpoint)
    {
      mountpoint->getRoot()->addEntry(entry);
    }
    else
    {
      if (callResourceSys(callbackResourceId, DIR_ADDENTRY, _implDefined, entry->resource->getId()))
      {
        entries.add(entry->getName(), entry);
      }
    }
  }
  u32int addEntryResource(u32int resourceId)
  {
    DEBUG_VERBOSE("Directory::addEntryResource(" << hex << resourceId << ").");
    Resource *r = resourceManager.request(resourceId);
    if (r)
      return addEntry((File*)r->getTarget());
    else
      return 0;
  }
  /**
     Remove a child entry from this directory.
  **/
  bool removeEntry(char *name)
  {
    // If we are a mountpoint, relay the command directly to the next filesystem.
    if (mountpoint)
    {
      return mountpoint->getRoot()->removeEntry(name);
    }
    else
    {
      if (!populated)
        populate();
      File *f = entries.remove(name);
      callResourceSys(callbackResourceId, DIR_REMOVEENTRY, _implDefined, (int)name, f->resource->getId());
    }
  }

  /**
    Finds the child entry with the specified name.
    Returns NULL if not found.
  **/
  u32int getEntryResource(char *name)
  {
    File *f = getEntry(name);
    if (f)
      return f->resource->getId();
    else
      return 0;
  }
  File *getEntry(char *name)
  {
    if (!populated)
      populate();
    
    if (!Kernel::strcmp(name, "."))
    {
      return (File *)this;
    }
    if (!Kernel::strcmp(name, ".."))
    {
      return (File *)parentDir;
    }
    // If we are a mountpoint, relay the command directly to the next filesystem.
    if (mountpoint)
    {
      return mountpoint->getRoot()->getEntry(name);
    }
    else
    {
      File *f = entries.lookup(name);
      u32int resId = -1;
      if (f)
        resId = f->resource->getId();
      int rId = callResourceSys(callbackResourceId, DIR_GETENTRY, _implDefined, (int)name, resId);
      if (rId == -1)
        return f;
      return (File*)resourceManager.request(rId)->getTarget();
    }
  }

  /**
     Returns the n'th entry in this directory.
  **/
  u32int getEntryNResource(u32int n)
  {
    File *f = getEntryN(n);
    if (f)
      return f->resource->getId();
    else
      return 0;
  }
  File *getEntryN(u32int n)
  {
    if (!populated)
      populate();
    if (mountpoint)
    {
      return mountpoint->getRoot()->getEntryN(n);
    }
    else
      return entries.getByIndex(n);
  }
  
  /**
     Returns the number of entries in this directory.
  **/
  u32int getNEntries()
  {
    if (!populated)
      populate();
    if (mountpoint)
    {
      return mountpoint->getRoot()->getNEntries();
    }
    else
      return entries.length();
  }
  
  /**
    Returns true if this directory's contents have been overridden
    by a mountpoint.
  **/
  bool isMountpoint()
  {
    return (bool)mountpoint;
  }

  /**
     Mounts filesystem into this directory. Fails if the 
     directory has a mountpoint already.
  **/
  bool mount(Filesystem *fs);
  bool mountResource(u32int resourceId)
  {
    Resource *r = resourceManager.request(resourceId);
    if (r)
      return mount((Filesystem*)r->getTarget());
    else
      return false;
  }
  
  /**
     Unmounts the filesystem from this directory. Returns false
     if there is no directory mounted. This function does not
     call the destructor for the dismounted filesystem.
  **/
  bool unmount()
  {
    if (!mountpoint)
    {
      return false;
    }
    else
    {
      mountpoint = NULL;
      return true;
    }
  }
  
  int populate()
  {
    callResourceSys(callbackResourceId, DIR_POPULATE, _implDefined, resource->getId());
    populated = true;
  }
  
  u32int mknod(FileTypes type, char *name, u32int resourceId, u32int impl);
  
protected:
  /**
    The map of filenames to entries.
  **/
  typedef Map<char *,File*,32> DirectoryEntryMap;
  DirectoryEntryMap entries;
  
  /**
    The mountpoint that this directory represents
    (can be NULL).
  **/
  class Filesystem *mountpoint;
  
  bool populated;
  
//   File::ImplDefined _implDefined;
};

#endif
